/* MediaUtil LLJTran - $RCSfile: Flashpix.java,v $
 * Copyright (C) 1999-2005 Dmitriy Rogatkin, Suresh Mahalingam.  All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 *  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *	$Id: Flashpix.java,v 1.3 2005/07/31 00:00:53 drogatkin Exp $
 *
 * Some ideas and algorithms were borrowed from:
 * Thomas G. Lane, and James R. Weeks
 */
package com.symaster.common.mediautil.image.jpeg;

/*
 See Flashpix Format Specification Version 1.0.2
 from July 2, 1998, Copyright Digital Imaging Group, Inc.
 */

import com.symaster.common.mediautil.gen.FileFormatException;
import com.symaster.common.mediautil.gen.Log;
import com.symaster.common.mediautil.gen.Rational;

import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;

public class Flashpix extends AbstractImageInfo<LLJTran> {
    public final static String FORMAT = "Flashpix";

    final static int HEADER_LENGTH = 512;

    final static byte[] SIGNATURE = {(byte) 0xd0, (byte) 0xcf, (byte) 0x11,
            (byte) 0xe0, (byte) 0xa1, (byte) 0xb1, (byte) 0x1a, (byte) 0xe1};

    Header header;

    byte[] fat;

    Icon icon;

    public Flashpix() {
    }

    public Flashpix(InputStream is, byte[] data, int offset, String name, LLJTran format)
            throws FileFormatException {
        super(is, data, offset, name, null, format);
    }

    public void readInfo() throws FileFormatException {
        try {
            data = new byte[HEADER_LENGTH - 8];
            read(is, data);
            offset += data.length;
            header = new Header();
            header.fill(data, -8);
            intel = header.intelByteOrder;
            if (Log.debugLevel >= Log.LEVEL_DEBUG)
                System.out.println("Header " + header + " of "
                        + getName());
            // skip to first FAT sector
            skip(is, header.fat[0] * header.sectorSize);
            offset += header.fat[0] * header.sectorSize;
            data = new byte[header.sectorSize]; // think about do it only once
            for (int i = 0; i < header.sectsFat; i++)
                if (Log.debugLevel >= Log.LEVEL_DEBUG)
                    System.out.println("Fat sector [" + i + "]=" + header.fat[i]);
            // read first FAT sector
            read(is, data);
            offset += data.length;
            if (Log.debugLevel >= Log.LEVEL_DEBUG)
                System.out.println("Sector " + header.sectDirStart + " marker "
                        + Integer.toHexString(s2n(header.sectDirStart * 4, 4)));
            fat = data;
            data = new byte[header.sectorSize];
            // read directory
            read(is, data);
            offset += data.length;
            DirectoryEntry root = new DirectoryEntry();
            root.fill(data, 0);
            if (Log.debugLevel >= Log.LEVEL_DEBUG)
                System.out.println("Root " + root);
            // go to child
            if (root._child < 0)
                return;
            DirectoryEntry child = new DirectoryEntry();
            child.fill(data, root._child * DirectoryEntry.ENTRY_SIZE);
            // start build tree here
            if (Log.debugLevel >= Log.LEVEL_DEBUG)
                System.out.println("Child " + child);
            DirectoryEntry sibchild = new DirectoryEntry();
            if (child._leftSib > 0) {
                sibchild.fill(data, child._leftSib * DirectoryEntry.ENTRY_SIZE);
                if (Log.debugLevel >= Log.LEVEL_DEBUG)
                    System.out.println("Left sib " + sibchild);
            }
        } catch (IOException e) {
            if (Log.debugLevel >= Log.LEVEL_ERROR)
                e.printStackTrace(System.err);
        }

    }

    public String getFormat() {
        return FORMAT;
    }

    public int getResolutionX() {

        return 0;
    }

    public int getResolutionY() {

        return 0;
    }

    public String getMake() {

        return null;
    }

    public String getModel() {

        return null;
    }

    public String getDataTimeOriginalString() {

        return null;
    }

    public float getFNumber() {

        return 0;
    }

    public Rational getShutter() {

        return new Rational(0, 1);
    }

    public boolean isFlash() {

        return false;
    }

    public String getQuality() {

        return null;
    }

    public float getFocalLength() {

        return 0;
    }

    public int getMetering() {

        return 0;
    }

    public int getExpoProgram() {

        return 0;
    }

    public String getReport() {

        return null;
    }

    public Icon getThumbnailIcon(Dimension size) {
        if (icon != null)
            return icon;
        if (getAdvancedImage() != null) {
            try {
                // try advanced image API
                return icon = getAdvancedImage().createThumbnailIcon(
                        getImageFile().getPath(), null);
            } catch (Throwable e) {
                if (Log.debugLevel >= Log.LEVEL_ERROR)
                    System.err.println(e);
            }
        }

        return null;
    }

}

class CLSID {
    final static int CLSID_LENGTH = 16;

    byte[] id = new byte[CLSID_LENGTH];

    void fill(byte[] data, int offset) {
        System.arraycopy(data, offset, id, 0,
                (CLSID_LENGTH + offset) < data.length ? CLSID_LENGTH
                        : data.length - offset);
    }

    public String toString() {
        StringBuffer result = new StringBuffer(CLSID_LENGTH * 2);
        for (int i = 0; i < id.length; i++)
            result.append(Integer.toHexString(id[i]));
        return result.toString();
    }
}

class Header extends BasicJpegIo {
    static final int SECTORSIZEOFF = 0x1e;
    static final int MINISECTORSIZEOFF = 0x20;
    static final int SECTSFATOFF = 0x2C;
    static final int SECTDIRSTARTOFF = 0x30;
    static final int MINISECTORCUTOFFOFF = 0x38;
    static final int SECTMINIFATSTARTOFF = 0x3C;
    static final int SECTSMINIFATOFF = 0x40;
    static final int SECTDIFSTARTOFF = 0x44;
    static final int SECTSDIFOFF = 0x48;
    static final int FATOFF = 0x4C;
    static final int FIRST_FAT_SIZE = 109;
    int minorVersion;
    int dllVersion;
    boolean intelByteOrder;
    int sectorSize;
    int miniSectorSize;
    int sectsFat;
    int sectDirStart;
    int miniSectorCutoff;
    int sectMiniFatStart;
    int sectsMiniFat;
    int sectDifStart;
    int sectsDif;
    int[] fat;

    void fill(byte[] data, int offset) {
        this.data = data;
        intelByteOrder = intel = true; // (data[0x1c-8] & 255) == 0xff && (data[0x1c-8+1] & 255) == 0xfe
        sectorSize = 1 << s2n(SECTORSIZEOFF + offset, 2);
        miniSectorSize = 1 << s2n(MINISECTORSIZEOFF + offset, 2);
        sectsFat = s2n(SECTSFATOFF + offset, 4);
        sectDirStart = s2n(SECTDIRSTARTOFF + offset, 4);
        miniSectorCutoff = s2n(MINISECTORCUTOFFOFF + offset, 4);
        sectMiniFatStart = s2n(SECTMINIFATSTARTOFF + offset, 4);
        sectsMiniFat = s2n(SECTSMINIFATOFF + offset, 4);
        sectDifStart = s2n(SECTDIFSTARTOFF + offset, 4);
        sectsDif = s2n(SECTSDIFOFF + offset, 4);
        fat = new int[FIRST_FAT_SIZE];
        for (int i = 0; i < FIRST_FAT_SIZE; i++)
            fat[i] = s2n(FATOFF + offset + 4 * i, 4);
    }

    public String toString() {
        String result = "Sector size " + sectorSize + ", mini sector size "
                + miniSectorSize + ", sectors in FAT " + sectsFat
                + ", directory sector " + sectDirStart
                + ", max size of mini stream " + miniSectorCutoff
                + ", mini FAT starts " + sectMiniFatStart
                + ", sectors in mini FAT " + sectsMiniFat
                + ", first DIF and numbers " + sectDifStart + ':' + sectsDif;
        return result;
    }
}

class DirectoryEntry extends BasicJpegIo {
    static final int ENTRY_SIZE = 128;

    static final int STGTY_INVALID = 0;

    static final int STGTY_STORAGE = 1;

    static final int STGTY_STREAM = 2;

    static final int STGTY_LOCKBYTES = 3;

    static final int STGTY_PROPERTY = 4;

    static final int STGTY_ROOT = 5;

    static final int DE_RED = 0;

    static final int DE_BLACK = 1;

    static final int AB_SIZE = 32;
    static final int CB_OFF = 0x40;
    static final int MSE_OFF = 0x42;
    static final int FLAGS_OFF = 0x43;
    static final int LEFTSIB_OFF = 0x44;
    static final int RIGHTSIB_OFF = 0x48;
    static final int CHILD_OFF = 0x4C;
    static final int CLSID_OFF = 0x50;
    static final int USERFLAGS_OFF = 0x60;
    static final int TIMECREATE_OFF = 0x64;
    static final int TIMEMODIFY_OFF = 0x6b;
    static final int SECTSTART_OFF = 0x74;
    static final int SIZE_OFF = 0x78;
    static final String[] types = {"INVALID", "STORAGE", "STREAM",
            "LOCKBYTES", "PROPERTY", "ROOT"};
    char[] _ab;
    int _cb;
    byte _mse;
    byte _flags;
    int _leftSib;
    int _rightSib;
    int _child;
    CLSID _id;
    int _userFlags;
    long _timeCreate;
    long _timeModify;
    int _sectStart;
    int _size;

    void fill(byte[] data, int offset) {
        this.data = data;
        intel = true;
        _mse = (byte) (s2n(MSE_OFF + offset, 1) & 255);
        if (_mse >= types.length || _mse < 0)
            _mse = 0;
        _cb = s2n(CB_OFF + offset, 2) / 2;
        _ab = new char[_cb];
        for (int i = 0; i < _cb && i < AB_SIZE; i++)
            _ab[i] = (char) /*s2n*/bs2i(offset + 2 * i
                    + (_mse == STGTY_ROOT ? 0 : 1), 2);
        _flags = (byte) (s2n(FLAGS_OFF + offset, 1) & 255);
        _leftSib = s2n(LEFTSIB_OFF + offset, 4);
        _rightSib = s2n(RIGHTSIB_OFF + offset, 4);
        _child = s2n(CHILD_OFF + offset, 4);
        _id = new CLSID();
        _id.fill(data, CLSID_OFF + offset);
        _userFlags = s2n(USERFLAGS_OFF + offset, 4);
        _timeCreate = s2n(TIMECREATE_OFF + offset, 8);
        _timeModify = s2n(TIMEMODIFY_OFF + offset, 8);
        _sectStart = s2n(SECTSTART_OFF + offset, 4);
        _size = s2n(SIZE_OFF + offset, 4);
    }

    public String toString() {
        return "Directory " + types[_mse] + " entry " + new String(_ab, 0, _cb)
                + ", mse " + _mse + ", child " + _child + ", left sib "
                + _leftSib + ", right sib " + _rightSib;
    }
}
